<?php

namespace Biz\service\advertisement;

use Biz\cache\advertisement\CPositionBaseInfo;
use Biz\cache\advertisement\CPositionClose;
use Biz\cache\advertisement\CPositionRules;
use Biz\model\advertisement\MHhzPositionPreview;
use Biz\model\advertisement\MPosition;
use Biz\model\advertisement\MPositionBaseInfo;
use Biz\model\advertisement\MPositionRules;
use Doraemon\pockets\constInterface\PopupServiceConf;
use Doraemon\pockets\constInterface\Position\PositionRulesConstInterface;
use Doraemon\service\position\BasePositionUserprofile;
use Doraemon\service\position\PositionConstInterface;
use Doraemon\service\position\UserProfileModule;
use Doraemon\tools\BlockType;
use Doraemon\tools\LinkFormat;
use Exception;


// todo 预览开关
// todo 唯一浏览开关

/**
 * @method  extCheckActive($v, $extInput, $default)
 * @method  uniquePos($v, $default)
 * @method  formatHits($list, $default)
 * @method  format($hit, $uid, $userProfileExt, $statSignExt, $default)
 * @method  extListMap($ids, $default)
 * @method  getExtInfo($baseInfo, $default)
 * @method  staticList($uid, $userProfileExt, $default)
 * @method  extUserProfileFl()
 */
class AdApi
{

    const PROCESS_LIST   = 1;
    const PROCESS_HIT    = 2;
    const PROCESS_RECORD = 4;
    const PROCESS_FORMAT = 8;
    const PROCESS_READ_CACHE = 16;
    const PROCESS_DISABLED_RULES = 32;
    const PROCESS_END = 64;
    public static $configs = [];
    public static $debug = false;
    protected $name = '';
    protected $class_name = '';
    /**
     * @var MPosition
     */
    protected $config = null;
    protected $conf_id = 0;
    protected $conf_class = null;
    protected $conf_style = null; // 开启占位
    protected $conf_rel = false; // 执行一次
    protected $conf_once = false;
    protected $conf_start_once = false;
    protected $conf_not_disturb = false; // 新用户免打扰
    protected $conf_new_disturb = false; // 新用户首次免打扰

    /**
     * 执行一次使用的 key
     * @var string
     */
    protected $lock_key;
    /**
     * 每次启动执行一次使用的 key
     * @var string
     */
    protected $start_lock_key;
    /**
     * 接口调用时间间隔使用的 key
     * @var string
     */
    protected $gap_key;
    /**
     * 新用户首次免打扰使用的 key
     * @var string
     */
    protected $new_lock_key;
    /**
     * 资源位配置
     * @var mixed
     */
    protected $conf_field; // 广告配置列表
    protected $source_list = null; // 广告列表
    protected $source_ids = []; // 广告列表 id 集合
    protected $lost_list = []; // 验证丢弃的列表
    protected $errors = []; // 预览标识
    protected $is_preview = false; // 当前用户
    protected $uid = 0; // 当前日期
    protected $now = null; // 格式化结果
    protected $format = true; // 用户分群数据
    /**
     * @var array|mixed
     */
    protected $rules;
    /**
     * 接口参数
     * @var array|mixed
     */
    protected $extend = [];
    /**
     * 接口参数
     * @var array|mixed
     */
    protected $stat_sign_extend = [];
    /**
     * 接口参数
     * @var array|mixed
     */
    protected $user_profile_extend = [];

    protected $user_profile_fl = [];
    /**
     * 流程选择
     * @var array|mixed
     */
    protected $process_options = -1;
    private $api_dir = '\\Biz\\service\\advertisement\\apiModule\\';
    private $read_cache = true;
    /**
     * @var AdUserProfileManagement
     */
    private $userProfile = null;


    use AdCounterManagement, AdAdapterManagement;

    /**
     * @return $this
    */
    public static function call($name): AdApi
    {
        return self::{$name}(0, [], [], [], 0);
    }

    public static function debug($name, $process = -1, $uid = 0, $user_profile_extend = [], $stat_sign_extend = [], $extend = []): array
    {
        /**
         * @var $instance self
         */
        $instance = self::{$name}($uid, $user_profile_extend, $stat_sign_extend, $extend, $process);

        return ['response' => $instance->getResponse()] + $instance->__debugInfo();
    }

    public static function api($method, $uid = 0, $user_profile_extend = [], $stat_sign_extend = [], $extend = [])
    {
        /**
         * @var $instance self
        */
        $instance = self::{$method}($uid, $user_profile_extend, $stat_sign_extend, $extend);

        $response = $instance->getResponse();

        $hasResponse = !empty($response);

        $subject = $_SERVER['REQUEST_URI'] ?? $_SERVER['SCRIPT_FILENAME'];
        $v = $instance->userProfile->app_version ?? null;
        \Doraemon\tools\Log::position_info("adRequest[$method]", compact('v','subject', 'hasResponse', 'uid', 'user_profile_extend', 'stat_sign_extend', 'extend'));

        return $response;
    }

    /**
     * 资源位统一入口
     * @return self
     */
    public static function __callStatic($name, $arguments)
    {
        $instance = new self;

        try {
            //init 1. 初始化参数
            $instance->uid = $arguments[0] ?? 0;
            $instance->now = date('Y-m-d H:i:s');
            $instance->user_profile_extend = $arguments[1] ?? [];
            $instance->stat_sign_extend = $arguments[2] ?? [];
            $instance->extend = $arguments[3] ?? [];
            $instance->process_options = $arguments[4] ?? -1;
            $instance->read_cache = $instance->process_options & self::PROCESS_READ_CACHE;

            //init 2. 解析方法名称,转换成[资源位]唯一标识符
            $instance->name = $instance->uncamelize($name);
            $instance->class_name = $instance->camelize($instance->name);

            if ($instance->name == 'home_popups') {
                $instance->popups_process();
            } else {
                $instance->process();
            }

        } catch (Exception $e) {

            $err = [
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'msg'  => $e->getMessage(),
            ];

            $instance->error(compact('name', 'arguments', 'err'));

            if (strpos($name, "CarouselBanner")) {
                $instance->source_list = [
                    'type'        => BlockType::TYPE_CAROUSEL_BANNER,
                    'banner_list' => [],
                    '_error'      => $instance->errors
                ];
            } else {
                $instance->source_list = false;
            }
        }

        return $instance;
    }

    /**
     * 资源位匹配流程
     * @throws Exception
     */
    private function popups_process()
    {
        //init. 查找[资源位]配置 && lock
        $this->findConfig();

        //1. 查询[资源位]列表
        if ($this->process_options & self::PROCESS_LIST) $this->findActiveList();

        //2. 过滤不符合要求的列表并且执行命中规则
        if ($this->process_options & self::PROCESS_HIT) $this->hit();

        //弹窗类型不受约束
        $popups_type = false;
        if ($this->source_list) $popups_type = $this->source_list['ext']['popups_type'];
        $popup =  $popups_type != PopupServiceConf::STYLE_FLOATING;
        if ($popup) {
            $this->conf_once = 0;
            $this->conf_start_once = 0;
        }

        if (!$popup && $this->conf_new_disturb && $this->uid && $this->isNewOne($this->uid)) {
            $this->new_lock_key = $this->newLockKey($this->uid, $this->conf_id);
            if (!$this->lock($this->new_lock_key)) {
                $this->process_options = self::PROCESS_FORMAT;
                $this->setLock($this->new_lock_key);
                $this->error('新用户首次不可见');
                $this->source_list = null;
            }
        }

        if (!$popup && $this->conf_not_disturb && $this->uid && $this->isNewOne($this->uid)) {
            $this->process_options = self::PROCESS_FORMAT;
            $this->error('新用户免打扰');
            $this->source_list = null;
        }

        if ($this->conf_once) {
            $this->lock_key = $this->lockKey($this->uid, $this->conf_id);
            // 每天请求一次的检查
            $this->onceLock($this->lock_key);
        }

        if ($this->conf_start_once) {
            $this->start_lock_key = $this->startLockKey($this->uid, $this->conf_id);
            // 每天请求一次的检查
            $this->onceLock($this->start_lock_key, '启动后资源位已经执行一次');
        }

        // 接口调用时间间隔检查
        $this->gap_key = $this->gapLockKey($this->uid, $this->conf_id);
        $this->onceLock($this->gap_key, '请求频繁');

        if ($this->process_options == self::PROCESS_FORMAT) $this->source_list = null;

        //3. 记录调用次数
        if ($this->process_options & self::PROCESS_RECORD) $this->record();

        //4. 格式化输出
        if ($this->process_options & self::PROCESS_FORMAT) $this->source_list = $this->format($this->source_list, $this->uid, $this->user_profile_extend, $this->stat_sign_extend, $this->source_list);

        //5.
        if ($this->process_options & self::PROCESS_END) $this->end();
    }

    /**
     * 资源位匹配流程
     * @throws Exception
     */
    private function process()
    {
        //init. 查找[资源位]配置 && lock
        $this->findConfig();

        if ($this->conf_new_disturb && $this->uid && $this->isNewOne($this->uid)) {
            $this->new_lock_key = $this->newLockKey($this->uid, $this->conf_id);
            if (!$this->lock($this->new_lock_key)) {
                $this->process_options = self::PROCESS_FORMAT;
                $this->setLock($this->new_lock_key);
                $this->error('新用户首次不可见');
            }
        }

        if ($this->conf_not_disturb && $this->uid && $this->isNewOne($this->uid)) {
            $this->process_options = self::PROCESS_FORMAT;
            $this->error('新用户免打扰');
        }

        if ($this->conf_once) {
            $this->lock_key = $this->lockKey($this->uid, $this->conf_id);
            // 每天请求一次的检查
            $this->onceLock($this->lock_key);
        }

        if ($this->conf_start_once) {
            $this->start_lock_key = $this->startLockKey($this->uid, $this->conf_id);
            // 每天请求一次的检查
            $this->onceLock($this->start_lock_key, '启动后资源位已经执行一次');
        }

        //1. 查询[资源位]列表
        if ($this->process_options & self::PROCESS_LIST) $this->findActiveList();

        //2. 过滤不符合要求的列表并且执行命中规则
        if ($this->process_options & self::PROCESS_HIT) $this->hit();

        //3. 记录调用次数
        if ($this->process_options & self::PROCESS_RECORD) $this->record();

        //4. 格式化输出
        if ($this->process_options & self::PROCESS_FORMAT) $this->source_list = $this->format($this->source_list, $this->uid, $this->user_profile_extend, $this->stat_sign_extend, $this->source_list);

        //5.
        if ($this->process_options & self::PROCESS_END) $this->end();
    }

    /**
     * 驼峰命名转下划线命名
     * 思路:
     * 小写和大写紧挨一起的地方,加上分隔符,然后全部转小写
     * @param string $camelCaps
     * @param string $separator
     * @return string
     */
    public static function uncamelize(string $camelCaps, string $separator = '_'): string
    {
        $str = preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps);

        $str = preg_replace('/([0-9])([A-Z])/', "$1" . $separator . "$2", $str);

        return strtolower($str);
    }

    /**
     * 下划线转驼峰
     * 思路:
     * step1.原字符串转小写,原字符串中的分隔符用空格替换,在字符串开头加上分隔符
     * step2.将字符串中每个单词的首字母转换为大写,再去空格,去字符串首部附加的分隔符.
     * @param string $uncamelize
     * @param bool   $class
     * @param string $separator
     * @return string
     */
    public static function camelize(string $uncamelize, bool $class = true, string $separator = '_'): string
    {
        $uncamelize = $separator . str_replace($separator, " ", strtolower($uncamelize));

        $uncamelize = ltrim(str_replace(" ", "", ucwords($uncamelize)), $separator);

        return $class ? ucwords($uncamelize) : $uncamelize;
    }

    /**
     * 查找[资源位]配置
     * @throws Exception
     */
    public function findConfig()
    {
        //通过名称获取配置
        if (empty(self::$configs)) {
            self::$configs = $this->positionList(MPosition::POSITION_PATTERN_ROUTE_MAP);
        }
        $this->config = self::$configs[$this->name] ?? [];
        if (empty($this->config)) throw new Exception('资源位基础信息未配置,name:' . $this->name);
        $this->config = new MPosition($this->config);
        $this->conf_id = $this->config->id;
        $this->conf_field = $this->config->drop_version ?? '0';
        $this->conf_once = AdConfManagement::findField(AdConfManagement::FIELD_ONCE, $this->conf_field);
        $this->conf_start_once = AdConfManagement::findField(AdConfManagement::FIELD_START_ONCE, $this->conf_field);
        $this->conf_not_disturb = AdConfManagement::findField(AdConfManagement::FIELD_NOT_DISTURB, $this->conf_field);
        $this->conf_new_disturb = AdConfManagement::findField(AdConfManagement::FIELD_ONCE_DISTURB, $this->conf_field);
        $this->conf_rel = $this->config->rel == MPosition::REL_OPEN;
        $this->conf_class = $this->api_dir . $this->class_name;
        $this->conf_style = $this->api_dir . $this->camelize($this->config['style_type']);
    }


    /**
     * 判断资源位是否只执行一次
     */
    public function onceLock($key, $errMsg = '资源位已经执行一次')
    {
        // 检查锁是否存在
        if ($this->uid && $this->lock($key)) {
            $this->process_options = self::PROCESS_FORMAT;
            $this->error($errMsg);
        }
    }

    /**
     * 追加错误信息
     */
    public function error($data)
    {
        $this->errors[] = $data;
    }

    /**
     * 查询[资源位]列表
     * 1. MPositionBaseInfo
     * 2. extListMap
     * 3. MPositionRules
     */
    public function findActiveList(): array
    {
        // 广告配置列表
        $this->activeList();

        // 静态列表
        $this->extendStaticList();

        // 预览数据
        if ($this->conf_rel) {
            $previewProjectRelationIds = MHhzPositionPreview::getAvailableProjectWithUserIdAndPositionId($this->uid, $this->conf_id);

            if ($previewProjectRelationIds && ($previewList = MPositionBaseInfo::apiPreviewList($previewProjectRelationIds))) {
                foreach ($previewList as &$v) {
                    $v['start_show_time'] = date('Y-m-d 00:00:00');
                    $v['end_show_time'] = date('Y-m-d 23:59:59');
                }
                $this->is_preview = true;
                $this->extendActiveList($previewList);
            }
        }

        if (!($this->process_options & self::PROCESS_DISABLED_RULES)) {
            $this->activeRules($this->source_ids);
            foreach ($this->source_list as &$v) {
                $v['_position_rules'] = $this->rules[$v['id']] ?? [];
            }
        }

        return $this->source_list;
    }

    /**
     * 广告配置列表
     */
    public function activeList()
    {
        // 取缓存判断
        // todo id → name
        $this->source_list = CPositionBaseInfo::getList($this->conf_id);

        // 没有缓存返回 false
        if ($this->source_list !== false && $this->read_cache) {
            $this->source_ids = empty($this->source_list) ? [] : array_column($this->source_list, 'id');
            return $this->source_list;
        }
        // todo id 换成 标识符
        $date = date('Y-m-d H:i:s');
        $list = MPositionBaseInfo::apiActiveList($this->conf_id, $date);

        $this->extendActiveList($list);

        CPositionBaseInfo::setList($this->conf_id, $this->source_list);

        return $this->source_list;
    }

    private function extendActiveList($list)
    {
        $this->source_ids = empty($list) ? [] : array_column($list, 'id');

        //扩展表的信息
        $extList = empty($this->source_ids) ? [] : $this->extListMap($this->source_ids, []);

        $this->source_list = empty($list) ? [] : array_map(function ($v) use ($extList) {
            $v['link_url'] = LinkFormat::generateSingleLink($v['link_type'], $v['link_params']);
            if ($v['show_type'] == MPositionBaseInfo::SHOW_TYPE_ALWAYS) $v['end_show_time'] = date('Y-m-d H:i:s', strtotime('+ 1 month'));
            $v['ext'] = $extList[$v['id']] ?? [];
            $v['_sort'] = [$v['pos'], 10 - $v['reason'], $v['sort'], $v['id']];
            return $v;
        }, $list);
    }

    private function extendStaticList()
    {
        //扩展表的信息
        $staticList = $this->staticList($this->uid, $this->user_profile_extend, []);

        if ($staticList) {
            $this->source_list = array_merge($this->source_list, $staticList);
            //排序
            $sort = array_column($this->source_list, '_sort');

            array_multisort($sort, SORT_ASC, $this->source_list);

        }

    }

    /**
     * 命中规则
     * 包含分群规则,api调用次数,共享规则
     * 分群规则依赖 $this->source_active_ids
     */
    public function hit()
    {
        // 配置解析
        $display = !AdConfManagement::findField(AdConfManagement::FIELD_DISPLAY, $this->conf_field);
        $share = !AdConfManagement::findField(AdConfManagement::FIELD_DISABLED_SHARE, $this->conf_field);
        $userprofileCheck = !AdConfManagement::findField(AdConfManagement::FIELD_DISABLED_USER_PROFILE, $this->conf_field);
        $close = AdConfManagement::findField(AdConfManagement::FIELD_CLOSE, $this->conf_field);

        // 预览的时候不做过滤
        // 获取分群规则
        if ($userprofileCheck) {
            $this->activeRules($this->source_ids);
            $fl = \Biz\service\advertisement\AdUserProfileManagement::getFlByActiveRules($this->rules);
            $extFl = $this->extUserProfileFl();
            if ($extFl) $fl = array_merge($fl, $extFl);
            $this->userProfile = AdUserProfileManagement::getUserProfile($this->uid, $this->user_profile_extend, $fl);
        }

        $hit_list = [];
        $continue = [];
        $exists = [];
        $nowDate = $this->now;
        foreach ($this->source_list as $v) {
            $id = $v['id'];
            $userProfileRules = $this->rules[$id] ?? [];
            try {
                //开始时间校验, (预览, 启动屏 不进行开始时间检验)
                $startCheck = $this->is_preview || $this->conf_id == PositionConstInterface::APP_START || $v['start_show_time'] <= $nowDate;
                if (!$startCheck) throw new Exception('未到开始时间');

                //结束时间校验
                $endCheck = $v['show_type'] == MPositionBaseInfo::SHOW_TYPE_ALWAYS || $v['end_show_time'] >= $nowDate;
                if (!$endCheck) throw new Exception('已到结束时间');

                //扩展校验
                $extCheck = $this->extCheckActive($v, $this->extend, true);
                if (!$extCheck) throw new Exception('扩展校验');

                //过滤用户手动关闭的资源位信息
                if (!$this->is_preview && $close && $this->userCloseSign($id, $v['close_type'])) throw new Exception('用户手动关闭');

                //分群规则验证
                if (!$this->is_preview && $userprofileCheck && !UserProfileModule::hitRule($this->userProfile, $userProfileRules)) throw new Exception('分群规则验证失败');

                //api调用次数验证
                $counterRules = $userProfileRules[PositionRulesConstInterface::RULE_TYPE_ACTION] ?? [];
                if (!$this->is_preview && $display && !empty($counterRules)) {
                    $time = $this->readApiCounter($counterRules, $this->uid, $this->conf_id, $id);

                    if (!$this->countValid($counterRules, $time)) throw new Exception('调用次数验证失败');
                }

                //共享规则处理
                if ($share && !$this->shareRules($v, $continue, $exists)) throw new Exception('共享条件限制');

                $hit_list[] = $v;

            } catch (Exception $e) {
                $this->lost_list[$id] = $e->getMessage();
            }
        }

        // 格式化列表
        $this->source_list = $this->formatHits($hit_list, $hit_list);
    }

    /**
     * 资源位的分群条件
     */
    public function activeRules($ids, $module_type = MPositionRules::MODULE_TYPE_POSITION)
    {
        $user_profile = [];

        $ids = is_array($ids) ? $ids : [$ids];

        if (empty($ids)) return $user_profile;

        $user_profile = $this->cacheRules($ids, $module_type);

        return $this->rules = $user_profile;
    }

    public static function cacheRules($ids, $module_type = MPositionRules::MODULE_TYPE_POSITION)
    {
        $user_profile = [];

        list($cache, $noCache) = CPositionRules::getList($module_type, $ids);

        if ($noCache) {
            //获取分群信息
            $where = [
                'position_base_info_id' => $noCache,
                'module_type'           => $module_type,
                'rule_status'           => MPositionRules::STATUS_NORMAL,
            ];

            $list = MPositionRules::select_master('*', $where);

            if ($list !== false) {
                $temp_user_profile = [];
                foreach ($list as $value) {
                    $temp_user_profile[$value['position_base_info_id']][$value['rule_type']] = UserProfileModule::decode($value['rule_type'], $value['rule_value']);
                }

                foreach ($noCache as $id) {
                    $user_profile[$id] = $temp_user_profile[$id] ?? [];
                }

                CPositionRules::setList($module_type, $user_profile);
            }
        }

        return $cache + $user_profile;
    }

    public function userCloseSign($id, $close_type): bool
    {
        if ($close_type == MPositionBaseInfo::CLOSE_TYPE_ONCE_INIT) {
            return $this->startClose($this->uid, $this->conf_id, $id);
        }

        if ($close_type == MPositionBaseInfo::CLOSE_TYPE_DAY) {
            return $this->dayClose($this->uid, $this->conf_id, $id);
        }

        return false;
    }

    /**
     * 处理共享规则
     * @see https://docs.google.com/document/d/1onb9larxu8F9HCOeIqAE-gVVGoiEvzigWRL1DbEIgN8/edit
     * @param $v
     * @param $continue
     * @param $exists
     * @return bool
     */
    public function shareRules($v, &$continue, &$exists): bool
    {
        $unique = $this->uniquePos($v, $v['pos']);

        $is_share = $v['is_share'];

        if (isset($continue[$unique])) return false;

        //如果是不分享的规则,阻断后面的规则
        if ($is_share == MPositionBaseInfo::IS_NOT_SHARE) {
            $continue[$unique] = true;

            if (isset($exists[$unique])) return false;
        }

        $exists[$unique] = true;

        return true;
    }

    public function end()
    {

    }

    /**
     * 记录输出 id
     */
    public function record()
    {
        //未登录用户不做记录
        if (empty($this->uid)) return;

        // 处理命中列表
        if (empty($this->source_list)) return;

        // 预览不产生记录
        if ($this->is_preview) return;

        // 处理单结构数据
        if (isset($this->source_list['id'])) {
            $this->callRecordMethod($this->source_list['id']);
            return;
        }

        foreach ($this->source_list as $v) {
            if (isset($v['id'])) {
                // 处理一维数组结构
                $this->callRecordMethod($v['id']);
            } else {
                // 处理二维数组结构
                if ($v) {
                    foreach ($v as $vv) {
                        if (isset($vv['id'])) $this->callRecordMethod($vv['id']);
                    }
                }
            }
        }
    }

    private function callRecordMethod($id)
    {
        $userProfileRules = $this->rules[$id] ?? [];
        $counterRules = $userProfileRules[PositionRulesConstInterface::RULE_TYPE_ACTION] ?? [];
        if ($id > 0) $this->recordApiCounter($counterRules, $this->uid, $this->conf_id, $id);
        if ($this->conf_once) {
            $this->setLock($this->lock_key);
        }

        if ($this->conf_start_once) {
            $this->setLock($this->start_lock_key);
        }

        if ($this->gap_key) {
            $this->setLock($this->gap_key, 8);
        }
    }

    /**
     * api 实现功能统一入口
     * @inherit \Doraemon\service\position\apiModule\AbstractApi
     */
    public function __call($name, $arguments)
    {
        $default = end($arguments);

        $params = array_slice($arguments, 0, -1);

        return $this->callFun($name, $params, $default);
    }

    /**
     *  后台列表 format 使用
    */
    public function formatAdmListExtInfo(&$v, $map)
    {
        return $this->callFun(__FUNCTION__, [&$v, $map], []);
    }

    /**
     * 执行配置类指定方法
     * @param       $method_name
     * @param array $param_arr
     * @param null  $default
     * @return mixed|null
     */
    public function callFun($method_name, array $param_arr = [], $default = null)
    {

        if (class_exists($this->conf_class) && method_exists($this->conf_class, $method_name)) return call_user_func_array([new $this->conf_class($this->conf_id, $this->userProfile), $method_name], $param_arr);

        if (class_exists($this->conf_style) && method_exists($this->conf_style, $method_name)) return call_user_func_array([new $this->conf_style($this->conf_id, $this->userProfile), $method_name], $param_arr);

        return $default;
    }

    /**
     * 获取返回结果
     * @return mixed
     */
    public function getResponse()
    {
        return $this->source_list;
    }

    /**
     * 获取错误信息
     */
    public function __debugInfo(): array
    {
        return [
            'errors' => $this->errors,
            'lost_list' => $this->lost_list,
        ];
    }

}